#include <QBitmap>
#include "math.h"
#include "cfloat"
#include "engine.h"
#include "minion.h"
#include "bullet.h"
#include "map.h"
#include "../GameAI/gameai.h"

const double PI = 3.14159265358979323846;

bool Engine::GameRunning = false;
Statistics Engine::GameStatistics;
Minion *Engine::FirstMinion = nullptr;
Bullet *Engine::FirstBullet = nullptr;
unsigned int Engine::TicksPerSecond = 1;
Map *Engine::CurrentMap = nullptr;

QPixmap *Engine::LoadPicture(const QString &Filename)
{
    QPixmap *picture = new QPixmap(Filename);
    picture->setMask(picture->createMaskFromColor(QColor(255,255,255)));
    return picture;
}

void Engine::LoadMap(const QString &Filename)
{
    if(CurrentMap) delete CurrentMap;

    CurrentMap = new Map(Filename);
}

void Engine::SpawnBullet(int Power, double X, double Y, double DestinationX, double DestinationY,
                                Minion *Target, double Speed, void *Picture, BulletFlags Flags, int Range)
{
    Bullet *NewBullet = new Bullet(Power, X, Y, DestinationX, DestinationY,
                                   Target, Speed, Picture, Flags, Range);

    if(!FirstBullet) FirstBullet = NewBullet;
    else
    {
        Bullet *LastBullet = FirstBullet;
        while(LastBullet->NextBullet)
        {
            LastBullet = LastBullet->NextBullet;
        }
        LastBullet->NextBullet = NewBullet;
    }
}

Bullet *Engine::RemoveBullet(Bullet *BulletToRemove)
{
    if(BulletToRemove == nullptr) return nullptr;

    Bullet *CurrentBullet = FirstBullet;
    if(FirstBullet == BulletToRemove)
    {
        CurrentBullet = FirstBullet = BulletToRemove->NextBullet;
    }
    else
    {
        while(CurrentBullet)
        {
            if(CurrentBullet->NextBullet == BulletToRemove)
            {
                CurrentBullet->NextBullet = BulletToRemove->NextBullet;
                break;
            }
            else CurrentBullet = CurrentBullet->NextBullet;
        }
    }

    delete BulletToRemove;
    return CurrentBullet;
}

Minion *Engine::SpawnMinion(const QString &Filename, int Team,
                            double X, double Y, double Orientation, QString PlayerName)
{
    Minion *NewMinion = new Minion(Filename, Orientation);
    if(NewMinion->MaxHealth > 0)
    {
        NewMinion->PlayerName = PlayerName.left(10);
        NewMinion->SetTeam(Team);
        NewMinion->X = X;
        NewMinion->Y = Y;
        NewMinion->DestinationX = X;
        NewMinion->DestinationY = Y;

        if(!FirstMinion) FirstMinion = NewMinion;
        else
        {
            Minion *LastMinion = FirstMinion;
            while(LastMinion->NextMinion)
            {
                LastMinion = LastMinion->NextMinion;
            }
            LastMinion->NextMinion = NewMinion;
        }
    }
    else
    {
        delete NewMinion;
        NewMinion = nullptr;
    }

    return NewMinion;
}

Minion *Engine::RemoveMinion(Minion *MinionToRemove)
{
    if(MinionToRemove == nullptr) return nullptr;

    GameAI::MinionRemoved(MinionToRemove);

    Minion *CurrentMinion = FirstMinion;
    if(FirstMinion == MinionToRemove)
    {
        CurrentMinion = FirstMinion = MinionToRemove->NextMinion;
    }
    else
    {
        while(CurrentMinion)
        {
            if(CurrentMinion->NextMinion == MinionToRemove)
            {
                CurrentMinion->NextMinion = MinionToRemove->NextMinion;
                break;
            }
            else CurrentMinion = CurrentMinion->NextMinion;
        }
    }

    CurrentMinion = FirstMinion;
    while(CurrentMinion)
    {
        CurrentMinion->MinionRemovalNotification(MinionToRemove);
        CurrentMinion = CurrentMinion->NextMinion;
    }
    Bullet *CurrentBullet = FirstBullet;
    while(CurrentBullet)
    {
        CurrentBullet->MinionRemovalNotification(MinionToRemove);
        CurrentBullet = CurrentBullet->NextBullet;
    }

    delete MinionToRemove;
    return CurrentMinion;
}

void Engine::EndGame()
{
    Engine::GameRunning = false;

    Minion *CurrentMinion = FirstMinion;
    while(CurrentMinion)
    {
        Minion *NextMinion = CurrentMinion->GetNextMinion();
        delete CurrentMinion;
        CurrentMinion = NextMinion;
    }
    FirstMinion = nullptr;

    Bullet *CurrentBullet = FirstBullet;
    while(CurrentBullet)
    {
        Bullet *NextBullet = CurrentBullet->GetNextBullet();
        delete CurrentMinion;
        CurrentBullet = NextBullet;
    }
    FirstBullet = nullptr;

    delete CurrentMap;
    CurrentMap = nullptr;
}

void Engine::Tick()
{
    CurrentMap->Tick();

    Bullet *CurrentBullet = FirstBullet;
    while(CurrentBullet)
    {
        CurrentBullet = CurrentBullet->Tick(); // reassign CurrentBullet, if Tick would remove it
        if(CurrentBullet) CurrentBullet = CurrentBullet->NextBullet;
    }

    Minion *CurrentMinion = FirstMinion;
    while(CurrentMinion)
    {
        CurrentMinion->MoveTick();
        CurrentMinion = CurrentMinion->NextMinion;
    }

    CurrentMinion = FirstMinion;
    while(CurrentMinion)
    {
        CurrentMinion->ShootTick();
        CurrentMinion = CurrentMinion->NextMinion;
    }
}

bool Engine::CheckCollision(Minion *MovedMinion, double X, double Y, bool IgnoreDeadFlag)
{
    if(MovedMinion->Flags & NO_COLLISION) return false;

    if(!MovedMinion->IsDead() || IgnoreDeadFlag)
    {
        Minion *CurrentMinion = FirstMinion;
        while(CurrentMinion)
        {
            if(CurrentMinion != MovedMinion && !(CurrentMinion->Flags & NO_COLLISION) &&
               (!CurrentMinion->IsDead() || CurrentMinion->IsWreck()))
            {
                if(!((CurrentMinion->X + CurrentMinion->Size < X - MovedMinion->Size ||
                   CurrentMinion->X - CurrentMinion->Size > X + MovedMinion->Size) &&
                   (CurrentMinion->Y + CurrentMinion->Size < Y - MovedMinion->Size ||
                   CurrentMinion->Y - CurrentMinion->Size > X + MovedMinion->Size)))
                {
                    // don't use Engine::GetDistance(CurrentMinion, MovedMinion) here,
                    // X and Y are not current coordinates of MovedMinion, but
                    // new coordinates which will be assigned, if no collision happens
                    if(Engine::GetDistance(CurrentMinion->X, CurrentMinion->Y, X, Y) <=
                                           CurrentMinion->Size + MovedMinion->Size) return true;
                }
            }
            CurrentMinion = CurrentMinion->NextMinion;
        }
    }

    foreach(QRect Obstacle, CurrentMap->Obstacles)
    {
        /*   5| 2 |6
         *  --+---+--
         *   1|Obs|3
         *  --+---+--
         *   8| 4 |7
         */
        // case 1 & 3
        if(Y >= Obstacle.y() && Y <= Obstacle.y() + Obstacle.height())
        {
            if((X < Obstacle.x() && X + MovedMinion->Size >= Obstacle.x()) ||
               (X > Obstacle.x() + Obstacle.width() &&
                X - MovedMinion->Size <= Obstacle.x() + Obstacle.width()))
                return true;
        }
        // case 2 & 4
        else if(X >= Obstacle.x() && X <= Obstacle.x() + Obstacle.width())
        {
            if((Y < Obstacle.y() && Y + MovedMinion->Size >= Obstacle.y()) ||
               (Y > Obstacle.y() + Obstacle.height() &&
                Y - MovedMinion->Size <= Obstacle.y() + Obstacle.height()))
                return true;
        }
        // case 5
        else if(X < Obstacle.x() && Y < Obstacle.y())
        {
            if(Engine::GetDistance(X, Y, Obstacle.x(), Obstacle.y()) <= MovedMinion->Size)
                return true;
        }
        // case 6
        else if(X > Obstacle.x() + Obstacle.width() && Y < Obstacle.y())
        {
            if(Engine::GetDistance(X, Y, Obstacle.x() + Obstacle.width(),
                                   Obstacle.y()) <= MovedMinion->Size)
                return true;
        }
        // case 7
        else if(X > Obstacle.x() + Obstacle.width() && Y > Obstacle.y() + Obstacle.height())
        {
            if(Engine::GetDistance(X, Y, Obstacle.x() + Obstacle.width(),
                                   Obstacle.y() + Obstacle.height()) <= MovedMinion->Size)
                return true;
        }
        // case 8
        else if(X < Obstacle.x() && Y > Obstacle.y() + Obstacle.height())
        {
            if(Engine::GetDistance(X, Y, Obstacle.x(),
                                   Obstacle.y() + Obstacle.height()) <= MovedMinion->Size)
                return true;
        }
    }

    return false;
}

bool Engine::IsVisible(Minion *CheckedMinion, int team)
{
    if(CheckedMinion->GetTeam() == team)
        return true;
    else if(CheckedMinion->DestinationMinion != nullptr &&
            Engine::GetDistance(CheckedMinion, CheckedMinion->DestinationMinion) <= CheckedMinion->Range)
        return true;
    else
    {
        Minion *AllyMinion = Engine::FirstMinion;
        while(AllyMinion)
        {
            if(AllyMinion->GetTeam() == team && !AllyMinion->IsDead())
            {
                if(Engine::GetDistance(AllyMinion, CheckedMinion) <= AllyMinion->GetSight())
                {
                    return true;
                }
            }
            AllyMinion = AllyMinion->GetNextMinion();
        }
        return false;
    }
}

void Engine::Move(double &X, double &Y, double DestinationX, double DestinationY, double Orientation, double Speed)
{
    double OffsetY = 1.33 * sin(Orientation * 2*PI) * Speed / TicksPerSecond;
    double OffsetX = 1.33 * cos(Orientation * 2*PI) * Speed / TicksPerSecond;


    if( ((OffsetX > 0) && (X + OffsetX > DestinationX)) ||
        ((OffsetX < 0) && (X + OffsetX < DestinationX)) )
    {
        X = DestinationX;
    }
    else
    {
        X += OffsetX;
    }

    if( ((OffsetY > 0) && (Y + OffsetY > DestinationY)) ||
        ((OffsetY < 0) && (Y + OffsetY < DestinationY)) )
    {
        Y = DestinationY;
    }
    else
    {
        Y += OffsetY;
    }
}

void Engine::Move(Minion *MovedMinion, double DestinationX, double DestinationY, double Orientation)
{
    double X = MovedMinion->X;
    double Y = MovedMinion->Y;
    double Speed = MovedMinion->Speed;

    Engine::Move(X, Y, DestinationX, DestinationY, Orientation, Speed);

    if(!Engine::CheckCollision(MovedMinion, X, Y))
    {
        MovedMinion->X = X;
        MovedMinion->Y = Y;
    }
}

bool Engine::Rotate(double &Orientation, double DestinationOrientation, double RotationSpeed)
{
    if(!equal(Orientation, DestinationOrientation, 0.01))
    {
        int RotationDirection = Engine::GetRotationDirection(Orientation,DestinationOrientation);

        Orientation += RotationDirection * RotationSpeed / Engine::TicksPerSecond;
        normalize(Orientation, 0.0, 1.0);

        if(Engine::GetRotationDirection(Orientation, DestinationOrientation) != RotationDirection)
            Orientation = DestinationOrientation;

        return true;
    }
    else
    {
        Orientation = DestinationOrientation;
        return false;
    }
}

void Engine::GetRespawnPoint(int Team, double &X, double &Y, int Point)
{
    QVector<QPoint> &RespawnPoints = CurrentMap->Teams[Team-1].RespawnPoints;
    int point;
    if(Point < 0 || Point >= RespawnPoints.count())
        point = rand() % RespawnPoints.count();
    else
        point = Point;
    X = RespawnPoints[point].x();
    Y = RespawnPoints[point].y();
}

Minion *Engine::FindNearestEnemy(Minion *OriginatorMinion, double &Distance)
{
    Minion *CurrentMinion = FirstMinion;
    double NearestDistance = DBL_MAX;
    Minion *NearestMinion = nullptr;
    while(CurrentMinion)
    {
        if(CurrentMinion->Team != OriginatorMinion->Team && !CurrentMinion->IsDead())
        {
            double Distance = Engine::GetDistance(CurrentMinion, OriginatorMinion);

            if(Distance < NearestDistance)
            {
                NearestDistance = Distance;
                NearestMinion = CurrentMinion;
            }
        }
        CurrentMinion = CurrentMinion->NextMinion;
    }
    Distance = NearestDistance;
    return NearestMinion;
}

Minion *Engine::FindNearestMinion(int X, int Y)
{
    Minion *CurrentMinion = FirstMinion;
    double NearestDistance = DBL_MAX;
    Minion *NearestMinion = nullptr;
    while(CurrentMinion)
    {
        double Distance = Engine::GetDistance(CurrentMinion->GetX(), CurrentMinion->GetY(), X, Y);

        if(Distance < NearestDistance && Distance <= CurrentMinion->Size)
        {
            NearestDistance = Distance;
            NearestMinion = CurrentMinion;
        }
        CurrentMinion = CurrentMinion->NextMinion;
    }
    return NearestMinion;
}

double Engine::GetDistance(double X1, double Y1, double X2, double Y2)
{
    return sqrt( pow(X1 - X2, 2) + pow(Y1 - Y2, 2) );
}

double Engine::GetDistance(Minion *FirstMinion, Minion *SecondMinion)
{
    double distance = Engine::GetDistance(FirstMinion->GetX(), FirstMinion->GetY(),
                                          SecondMinion->GetX(), SecondMinion->GetY());
    return distance - FirstMinion->GetSize() - SecondMinion->GetSize();
}

double Engine::GetDestinationOrientation(double X, double Y, double DestinationX, double DestinationY)
{
    double ret = atan2(DestinationY - Y, DestinationX - X) / (2*PI);
    if(ret < 0.0) ret += 1.0;
    return ret;
}

int Engine::GetRotationDirection(double Current, double Destination)
{
    double diff = Destination - Current;

    if(diff < -0.5)
        return 1; // clockwise
    else if(diff >= -0.5 && diff < 0.0)
        return -1; // anti-clockwise
    else if(diff >= 0.0 && diff < 0.5)
        return 1;
    else //if(diff >= 0.5)
        return -1;
}
